package wiki.kaizen.cloud.security.session.support.filter;

import lombok.Setter;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.iherus.shiro.cache.redis.RedisCache;
import wiki.kaizen.cloud.security.session.properties.SecurityProperties;
import wiki.kaizen.cloud.security.session.util.ISecurityUtil;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;

/**
 *
 * 自定义 重复登录 控制拦截器
 * @author xeepoo
 * */
public class KickoutControlFilter extends AccessControlFilter {

    @Setter
    private SecurityProperties.AccessAccount accessAccount;

    @Setter
    private SessionManager sessionManager;

    private RedisCache<String, Deque<Serializable>> cache;

    public void setCache(RedisCache<String, Deque<Serializable>> cache) {
        this.cache = cache;
    }
    /**
     * 表示是否允许访问；mappedValue就是[urls]配置中拦截器参数部分，如果允许访问返回true，否则false；
     * (感觉这里应该是对白名单（不需要登录的接口）放行的)
     * 如果isAccessAllowed返回true则onAccessDenied方法不会继续执行
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object o) throws Exception {

        Subject subject = getSubject(request, response);

        /*
        * 没有登录的 直接放行
        * */
        if (!subject.isAuthenticated()&&!subject.isRemembered()){
            return true;
        }
        Session session = subject.getSession();

        String name = accessAccount.getKickoutName();
        /*
        * 先进性是否已被踢出的判断 被踢出的直接 拦下
        * */
        Boolean kickout = (Boolean)session.getAttribute(name);
        if (kickout != null&& kickout ){
            return false;
        }

        String key = ISecurityUtil.key(session);

        Serializable sessionId = session.getId();
        /*
        * 读取缓存 没有就存入
        * */
        Deque<Serializable> deque = cache.get(key);

        /*
        * 用户之前没有登录过 deque里就不存在
        * */
        if (deque == null){
            deque = new LinkedList<>();
            deque.push(sessionId);

        }else if(!deque.contains(sessionId)){
            deque.push(sessionId);

        }
        int size = deque.size();
        int max = accessAccount.getMax();
        while (size > max){

            Serializable kickoutSessionId =
                accessAccount.getKickoutAfter()? deque.removeFirst() : deque.removeLast() ;
            size = deque.size();
            try{
                Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));

                if (kickoutSession != null){
                    kickoutSession.setAttribute(name,true);
                }
            }catch (Exception ignore){}

        }
        /*
         *更新队列
         * */
        cache.put(key,deque);

        /**
         * 再进行一次判断 是否被踢出
         * */
        Boolean after = (Boolean)session.getAttribute(name);
        return after == null || !after;
    }


    /**
     * 进到这里面的 就是 isAccessAllowed return false的
     * 都进行退出操作
     * */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//退出
        Subject subject = getSubject(request, response);
        subject.logout();
        saveRequest(request);
        WebUtils.issueRedirect(request, response, accessAccount.getKickoutUrl());
        return false;
    }

}
